#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright (c) 2018, Kontron Europe GmbH
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

from __future__ import print_function

import argparse
import copy
import dateutil.parser
import json
import numpy
import sys

from collections import OrderedDict
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def plot(filename, data, stats, props):
    all_data = []
    labels = []

    for k,v in data.items():
        if props['ignorets'] and k in props['ignorets']:
            continue
        all_data.append(v)
        labels.append(k)

    plt.figure(figsize=(10,8))
    plt.suptitle(props['plottitle'], fontsize=14, fontweight='bold')
    plt.boxplot(all_data)
    if props['ymin']:
        plt.ylim(ymin=props['ymin'] * 1000)
    if props['ymax']:
        plt.ylim(ymax=props['ymax'] * 1000)
    plt.grid(axis='y')
    plt.xticks(range(1,len(labels)+1), labels, rotation=10)
    if props['relmode']:
        plt.ylabel("Duration $\Delta$T ($\mu$s)")
    else:
        plt.ylabel("Absolute time ($\mu$s)")
    locs, _ = plt.yticks()
    plt.yticks(locs, [l / 1000.0 for l in locs])
    text = "values: %(total)d in total, %(invalid)d invalid" % stats
    plt.text(0.005, 0.995, text, transform=plt.gca().transAxes, verticalalignment='top')
    plt.savefig(filename)
    plt.close()

def update_data(data, ts, relmode=False):
    # convert timestamp in seconds since XXX
    # 2021-01-26T14:50:57.428000000 -> 1611672657428000000
    values = [int(numpy.datetime64(v)) for v in ts['values']]

    # check if a timestamp is 0
    if min(values) == 0:
        return True

    # substract first timestamp value from the follwing
    values = [v - values[0] for v in values]

    if (relmode):
        values[1:] = [b-a for a,b in zip(values, values[1:])]

    for (i,n) in enumerate(ts['names']):
        data[n].append(values[i])

    return False

def main(args=None):
    parser = argparse.ArgumentParser(
        description='trace')
    parser.add_argument('--y-min', dest='ymin', type=float,
                        help='Set minimum displayed values on y-axes.')
    parser.add_argument('--y-max', dest='ymax', type=float,
                        help='Set maximum displayed values on y-axes.')
    parser.add_argument('--title', dest='plottitle', type=str,
                        default='Packet timestamps',
                        help='Set plot title.')
    parser.add_argument('--ignore-ts', dest='ignorets', type=str,
                        action='append',
                        help='Do not display specified timestamps.')
    parser.add_argument('--rel-mode', dest='relmode', action='store_true',
                        help='Use the diffs between two subsequent timestamps.')
    parser.add_argument('-c', '--count', dest='count', type=int,
                        help='Dump report after each COUNT packets.')
    parser.add_argument('infile', nargs='?', type=argparse.FileType('r'),
                        help='Input file (default is STDIN).', default=sys.stdin)
    parser.add_argument('outfile', type=str, help='Output file.')
    args = parser.parse_args(args)

    data = OrderedDict()
    props = dict(ymin=args.ymin,
                 ymax=args.ymax,
                 ignorets=args.ignorets,
                 relmode=args.relmode,
                 plottitle=args.plottitle)
    stats = None

    try:
        for line in args.infile:
            line = line.strip()
            if not line:
                continue

            try:
                j = json.loads(line)
            except ValueError as e:
                print(e, file=sys.stderr)
                pass

            try:
                if j['type'] == 'rx-packet':
                    ts = j['object']['timestamps']
                    if not data:
                        data = OrderedDict()
                        for n in ts['names']:
                            data[n] = list()
                    if not stats:
                        stats = dict(total=0, invalid=0)
                    error = update_data(data, ts, relmode=args.relmode)
                    if error:
                        stats['invalid'] += 1
                    stats['total'] += 1
                    if stats['total'] == args.count:
                        plot(args.outfile, data, stats, props)
                        stats = None
                        data = None
                else:
                    print(line, file=sys.stdout)
                    sys.stdout.flush()
            except KeyError as e:
                print(e, file=sys.stderr)
                pass

    except KeyboardInterrupt as e:
        pass

    plot(args.outfile, data, stats, props)


if __name__ == '__main__':
    main()
